﻿using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;

namespace FastCreateProject.Helpers
{
    public class Server
    {
        public delegate void RunningEventHadler(string address);

        public delegate void ReceivedRequestEventHadler(string requestContent, string ip);

        public delegate void BeforeResponseEventHadler(string responseContent);

        public delegate void ErrorEventHadler(Exception ex);

        public event RunningEventHadler Runing;

        public event ReceivedRequestEventHadler ReceivedRequest;

        public event BeforeResponseEventHadler BeforeResponse;

        public event ErrorEventHadler Error;

        public ManualResetEvent allDone = new ManualResetEvent(false);

        private readonly StartOption _option;

        public Server(StartOption option)
        {
            _option = option;
        }

        private Type[] _types;

        public void Start()
        {
            if (Runing == null)
            {
                Runing = Server_Runing;
            }
            if (ReceivedRequest == null)
            {
                ReceivedRequest = Server_ReceivedRequest;
            }
            if (BeforeResponse == null)
            {
                BeforeResponse = Server_BeforeResponse;
            }
            if (Error == null)
            {
                Error = Server_Error;
            }
            if (_option.StaticFileExtensions==null)
            {
                _option.StaticFileExtensions=new Dictionary<string, string>();
            }
            if (_option.StaticFileExtensions.Keys.Count==0)
            {
                _option.StaticFileExtensions.Add(".txt", "text/plain");
                _option.StaticFileExtensions.Add(".html", "text/html");
                _option.StaticFileExtensions.Add(".css", "text/css");
                _option.StaticFileExtensions.Add(".js", "application/x-javascript");
                _option.StaticFileExtensions.Add(".ico", "image/x-icon");
                _option.StaticFileExtensions.Add(".jpeg", "image/jpeg");
                _option.StaticFileExtensions.Add(".jpg", "image/jpeg");
                _option.StaticFileExtensions.Add(".png", "image/png");
                _option.StaticFileExtensions.Add(".gif", "image/gif");
                _option.StaticFileExtensions.Add(".woff", "font/woff");
                _option.StaticFileExtensions.Add(".woff2", "font/woff2");
                _option.StaticFileExtensions.Add(".ttf", "font/ttf");
            }
            if (_option.Routes==null ||  _option.Routes.Count==0)
            {
                throw new WithCodeException(ErrorCodeEnum.CodeError, "请先配置路由");
            }
            var assemblyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, _option.Assembly);
            if (!File.Exists(assemblyFile))
            {
                throw new WithCodeException((int)ErrorCodeEnum.CodeError, $"未找到程序集：{assemblyFile}");
            }

            var assembly = Assembly.LoadFile(assemblyFile);
            _types = assembly.GetTypes();

            var ip = "127.0.0.1";
            var ipAddress = IPAddress.Parse(ip);
            var localEndPoint = new IPEndPoint(ipAddress, _option.Port);
            var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            listener.Bind(localEndPoint);
            listener.Listen(100);

            Runing?.Invoke(string.Format("http://{0}:{1}", ip, _option.Port));

            while (true)
            {
                allDone.Reset();
                listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                allDone.WaitOne();
            }
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                allDone.Set();
                var listener = (Socket)ar.AsyncState;
                var client = listener.EndAccept(ar);
                var state = new ClientStateObject
                {
                    WorkSocket=client,
                    ReceiveBuffer=new byte[_option.BufferSize]
                };
                state.WorkSocket.BeginReceive(state.ReceiveBuffer, 0, state.ReceiveBuffer.Length, 0, new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception ex)
            {
                Error?.Invoke(ex);
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                var state = (ClientStateObject)ar.AsyncState;
                if (state.WorkSocket.Connected)
                {
                    int bytesRead = state.WorkSocket.EndReceive(ar);
                    if (bytesRead > 0)
                    {
                        var content = Encoding.UTF8.GetString(state.ReceiveBuffer, 0, bytesRead);
                        var ip = ((IPEndPoint)state.WorkSocket.RemoteEndPoint).Address.ToString();
                        ReceivedRequest?.Invoke(content, ip);
                        Send(state, content);
                    }
                }
            }
            catch (Exception ex)
            {
                Error?.Invoke(ex);
            }
        }

        private void Send(ClientStateObject state, string data)
        {
            var req = CreateHttpRequest(data);
            var ip = ((IPEndPoint)state.WorkSocket.RemoteEndPoint).Address.ToString();
            req.IP = ip;

            var rsp = CreateHttpResponse(req);
            var sb = new StringBuilder();
            sb.AppendFormat("HTTP/{0} {1} {2}\r\n", rsp.Version, rsp.Code, rsp.Message);
            foreach (var key in rsp.Headers.Keys)
            {
                sb.AppendFormat("{0}:{1}\r\n", key, rsp.Headers[key]);
            }

            if (!string.IsNullOrEmpty(rsp.Text))
            {
                sb.AppendFormat("\r\n");
                sb.AppendFormat("{0}", rsp.Text);
            }

            if (rsp.Blob!=null)
            {
                sb.AppendFormat("\r\n");
            }

            var content = sb.ToString();
            var bytes = Encoding.UTF8.GetBytes(content).ToList();
            if (rsp.Blob!=null)
            {
                bytes.AddRange(rsp.Blob);
            }
            var bytesData = bytes.ToArray();
            state.SendData = content;
            state.WorkSocket.BeginSend(bytesData, 0, bytesData.Length, 0, new AsyncCallback(SendCallback), state);
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                var state = (ClientStateObject)ar.AsyncState;
                if (state.WorkSocket.Connected)
                {
                    state.WorkSocket.EndSend(ar);
                    BeforeResponse?.Invoke(state.SendData);

                    state.WorkSocket.Shutdown(SocketShutdown.Both);
                    state.WorkSocket.Close();
                }
            }
            catch (Exception ex)
            {
                Error?.Invoke(ex);
            }
        }

        private HttpRequestModel CreateHttpRequest(string data)
        {
            if (string.IsNullOrEmpty(data))
            {
                return null;
            }

            var req = new HttpRequestModel
            {
                Headers = new Dictionary<string, string>()
            };

            var lines = data.Split(new string[] { "\r\n" }, StringSplitOptions.None);
            var parts = lines[0].Split(' ');
            if (parts.Length >= 3)
            {
                req.HttpMethod = parts[0].ToUpper();
                req.Url = parts[1];
                req.Version = parts[2].ToUpper().Replace("HTTP", "").Replace("/", "");
            }

            if (lines.Length > 1)
            {
                var headerRange = lines.Length;
                var blankLineIndex = GetBlankLineIndex(lines);
                if (blankLineIndex >= 0)
                {
                    headerRange = blankLineIndex;

                    var body = "";
                    for (var i = blankLineIndex + 1; i < lines.Length; i++)
                    {
                        body += lines[i];
                    }

                    if (!string.IsNullOrEmpty(body))
                    {
                        req.Body = body;
                    }
                }

                for (var i = 1; i < headerRange; i++)
                {
                    var headerLineParts = lines[i].Split(':');
                    if (headerLineParts.Length > 1)
                    {
                        var sb = "";
                        for (var k = 1; k < headerLineParts.Length; k++)
                        {
                            sb += headerLineParts[k];
                        }
                        req.Headers[headerLineParts[0]] = sb;
                    }
                }
            }

            return req;
        }

        private HttpResponseModel CreateHttpResponse(HttpRequestModel req)
        {
            var contentRsp = new HttpResponseActionModel();
            var rsp = new HttpResponseModel
            {
                Code = 200,
                Message = "OK",
                Version = req.Version,
                Headers = new Dictionary<string, string>()
            };

            if (string.IsNullOrEmpty(req.Url))
            {
                contentRsp = Get404Page();
                rsp.Code = 404;
                rsp.Message = "Not Found";
            }
            else
            {
                var url_parts = req.Url.Split('?');
                var url = url_parts[0];
                var parts = url.TrimStart('/').Split('/');
                var fileName = parts.Last();
                var ext = Path.GetExtension(fileName);
                var key = _option.StaticFileExtensions.Keys.Where(w => w.ToLower() == ext.ToLower()).FirstOrDefault();
                if (!string.IsNullOrEmpty(key))
                {
                    contentRsp = GetFileContent(req.Url, _option.StaticFileExtensions[key]);
                    if (contentRsp == null)
                    {
                        contentRsp = Get404Page();
                        rsp.Code = 404;
                        rsp.Message = "Not Found";
                    }
                }
                else
                {
                    if (parts.Length == 1 && string.IsNullOrEmpty(parts[0]))
                    {
                        contentRsp = GetIndexPage();
                    }
                    else
                    {
                        try
                        {
                            contentRsp = GetActionContent(req, rsp, parts);
                        }
                        catch (Exception ex)
                        {
                            Error?.Invoke(ex);

                            contentRsp = Get500Page();
                            rsp.Code=500;
                            rsp.Message="Server Error";
                        }

                        if (contentRsp == null)
                        {
                            contentRsp = Get404Page();
                            rsp.Code=404;
                            rsp.Message="Not Found";
                        }
                    }
                }
            }

            rsp.Text = contentRsp.Text;
            rsp.Blob = contentRsp.Blob;
            rsp.Headers["Date"] = DateTime.Now.ToString("R");
            rsp.Headers["Server"] = "Inmoe";
            rsp.Headers["Content-Type"] = contentRsp.ContentType;
            rsp.Headers["Content-Length"] = contentRsp.BodyLength.ToString();
            if (!string.IsNullOrEmpty(contentRsp.DownloadFileName))
            {
                rsp.Headers["Content-Disposition"] = $"attachment; filename={contentRsp.DownloadFileName}; filename*=UTF-8''{contentRsp.DownloadFileNameUTF8}";
            }
            return rsp;
        }

        private HttpResponseActionModel GetActionContent(HttpRequestModel req, HttpResponseModel rsp, string[] args)
        {
            var typeName = default(string);
            var methodName = default(string);
            var ttype = default(Type);
            var tmethod = default(MethodInfo);
            var lastArgs = "";
            var route = _option.Routes.Where(w => w.Level == args.Length - 1).FirstOrDefault();
            if (route != null)
            {
                if (args.Length == 1)
                {
                    typeName = route.NS + ".HomeController";
                    methodName = "Index";
                    lastArgs = args[0];
                }
                else if (args.Length == 2)
                {
                    typeName = route.NS + ".HomeController";
                    methodName = args[0];
                    lastArgs = args[1];
                }
                else
                {
                    var sb = "";
                    for (var i = 0; i < args.Length - 2; i++)
                    {
                        sb += "." + args[i];
                    }
                    typeName = route.NS + sb + "Controller";
                    methodName = args[args.Length - 2];
                    lastArgs = args[args.Length - 1];
                }

                GetTypeAndMethod(typeName, methodName, ref ttype, ref tmethod);
            }
            else
            {
                route = _option.Routes.Where(w => w.Level == args.Length).FirstOrDefault();
                if (route != null)
                {
                    if (args.Length == 1)
                    {
                        typeName = route.NS + ".HomeController";
                        methodName = "Index";
                    }
                    else
                    {
                        var sb = "";
                        for (var i = 0; i < args.Length - 1; i++)
                        {
                            sb += "." + args[i];
                        }
                        typeName = route.NS + sb + "Controller";
                        methodName = args[args.Length - 1];
                    }

                    GetTypeAndMethod(typeName, methodName, ref ttype, ref tmethod);
                }
            }

            if (ttype!=null && tmethod!=null)
            {
                var response = new HttpResponseActionModel();
                var type_option = GetControllerOption(req);
                var type_obj = Activator.CreateInstance(ttype);
                var contentRsp = (HttpResponseActionModel)tmethod.Invoke(type_obj, new object[] { type_option, lastArgs, req });
                return contentRsp;
            }

            return null;
        }

        private ControllerOption GetControllerOption(HttpRequestModel req)
        {
            var wwwroot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
            var userAgent = default(string);
            var userAgentKey = req.Headers.Keys.Where(w => w.ToLower().Replace("-", "")=="useragent").FirstOrDefault();
            if (!string.IsNullOrEmpty(userAgentKey))
            {
                userAgent=req.Headers[userAgentKey];
            }

            var isMobileRequest = WebHelper.IsMobileRequest(userAgent);
            return new ControllerOption
            {
                WWWRoot= wwwroot,
                IP=req.IP,
                UserAgent= userAgent,
                IsMobileRequest= isMobileRequest
            };
        }

        private void GetTypeAndMethod(string typeName, string methodName, ref Type _type, ref MethodInfo _method)
        {
            var ttype = _types.Where(w => w.FullName.ToLower() == typeName.ToLower()).FirstOrDefault();
            if (ttype == null)
            {
                return;
            }

            var tmethods = ttype.GetMethods();
            var tmethod = tmethods.Where(w => w.Name.ToLower() == methodName.ToLower()).FirstOrDefault();
            if (tmethod == null)
            {
                return;
            }

            _type=ttype;
            _method=tmethod;
        }

        private HttpResponseActionModel GetIndexPage()
        {
            return GetDefinedPage(_option.IndexPage, "首页配置不正确");
        }

        private HttpResponseActionModel Get404Page()
        {
            return GetDefinedPage(_option.Error404Page, "404页面配置不正确");
        }

        private HttpResponseActionModel Get500Page()
        {
            return GetDefinedPage(_option.Error500Page, "500页面配置不正确");
        }

        private HttpResponseActionModel GetDefinedPage(string filePath, string message)
        {
            var contentType = "text/html;charset=utf-8";
            var rsp = GetFileContent(filePath, contentType);
            if (rsp != null)
            {
                return rsp;
            }

            var content = message;
            var contentLength = Encoding.UTF8.GetBytes(content).Length;
            return new HttpResponseActionModel
            {
                ContentType=contentType,
                Text=content,
                BodyLength=contentLength
            };
        }

        private HttpResponseActionModel GetFileContent(string filePath, string contentType)
        {
            var paths = new List<string> { AppDomain.CurrentDomain.BaseDirectory, "wwwroot" };
            var parts = filePath.TrimStart('/').Split('/');
            paths.AddRange(parts);

            var fullName = Path.Combine(paths.ToArray());
            if (File.Exists(fullName))
            {
                var bytes = File.ReadAllBytes(fullName);
                var contentRsp = new HttpResponseActionModel
                {
                    ContentType= contentType,
                    BodyLength=bytes.Length
                };
                if (contentType == "application/x-javascript" || contentType.StartsWith("text/"))
                {
                    contentRsp.Text = Encoding.UTF8.GetString(bytes);
                }
                else
                {
                    contentRsp.Blob = bytes;
                }

                return contentRsp;
            }

            return null;
        }

        private int GetBlankLineIndex(string[] lines)
        {
            for (var i = 0; i < lines.Length; i++)
            {
                if (string.IsNullOrEmpty(lines[i]))
                {
                    return i;
                }
            }

            return -1;
        }

        private void Server_Error(Exception ex)
        {
            LogHelper.WriteErrorAsync(ex.ToString());
        }

        private void Server_BeforeResponse(string responseContent)
        {
            if (responseContent.Length<100)
            {
                LogHelper.WriteAsync(responseContent);
            }
        }

        private void Server_ReceivedRequest(string requestContent, string ip)
        {
            LogHelper.WriteAsync($"{ip}\r\n{requestContent}");
        }

        private void Server_Runing(string address)
        {
            LogHelper.WriteAsync($"服务已启动：{address}");
        }
    }

    public class ClientStateObject
    {
        public Socket WorkSocket { get; set; }

        public byte[] ReceiveBuffer { get; set; }

        public string SendData { get; set; }
    }

    public class StartOption
    {
        public int Port { get; set; }

        public string Assembly { get; set; }

        public string IndexPage { get; set; }

        public string Error404Page { get; set; }

        public string Error500Page { get; set; }

        public int BufferSize { get; set; }

        public List<RouteItem> Routes { get; set; }

        public Dictionary<string, string> StaticFileExtensions { get; set; }
    }

    public class RouteItem
    {
        public int Level { get; set; }

        public string NS { get; set; }
    }

    public class HttpModel
    {
        public string Version { get; set; }

        public Dictionary<string, string> Headers { get; set; }
    }

    public class HttpRequestModel : HttpModel
    {
        public string HttpMethod { get; set; }

        public string Url { get; set; }

        public string Body { get; set; }

        public string IP { get; set; }
    }

    public class HttpResponseModel : HttpModel
    {
        public int Code { get; set; }

        public string Message { get; set; }

        public string Text { get; set; }

        public byte[] Blob { get; set; }

        public int BodyLength { get; set; }
    }

    public class HttpResponseActionModel
    {
        public string ContentType { get; set; }

        public string Text { get; set; }

        public byte[] Blob { get; set; }

        public int BodyLength { get; set; }

        public string DownloadFileName { get; set; }

        public string DownloadFileNameUTF8 { get; set; }
    }

    public class Controller
    {
        public virtual void BeforeExecuteAction()
        {

        }

        public HttpResponseActionModel Json(object obj)
        {
            var content = System.Text.Json.JsonSerializer.Serialize(obj);
            var contentLength = Encoding.UTF8.GetBytes(content).Length;
            return new HttpResponseActionModel
            {
                ContentType = "application/json",
                Text = content,
                BodyLength = contentLength
            };
        }
    }

    public class ControllerOption
    {
        public string WWWRoot { get; set; }

        public string IP { get; set; }

        public string UserAgent { get; set; }

        public bool IsMobileRequest { get; set; }
    }

    /// <summary>
    /// 接口
    /// </summary>
    public class ApiItem
    {
        /// <summary>
        /// 名称
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 地址
        /// </summary>
        public string Url { get; set; }
    }
}
